/*
	implementation of spu job manager
	DMA memory mappings can be issued in any order
*/
#include "StdAfx.h"

#include <IRenderAuxGeom.h>

#include <CryThread.h>
//#include <sys/dbg.h>
//#include <cell/gcm/gcm_macros.h>
#include <IJobManager.h>
#include "SPUDriver/SPU.h"
#include "PPU.h"
#include "JobManager.h"
#include "SPUJobBase.h"

#include "SPUDriver/CodePage/SPUPageLayout.h"
#include "FallbackBackend/FallBackBackend.h"
#include "PCBackEnd/ThreadBackEnd.h"
#include "BlockingBackend/BlockingBackEnd.h"

#include "../System.h"
#include "../CPUDetect.h"




 
namespace JobManager {
namespace Detail {
	


	/////////////////////////////////////////////////////////////////////////////
	// functions to convert between an index and a semaphore handle by salting
	// the index with a special bit (bigger than the max index), this is requiered
	// to have a index of 0, but have all semaphore handles != 0
	// a TSemaphoreHandle needs to be 16 bytes, it shared one word (4 byte) with the number of jobs running in a syncvar
	enum { nSemaphoreSaltBit = 0x8000 };	// 2^15 (highest bit)

	static TSemaphoreHandle IndexToSemaphoreHandle( uint32 nIndex )			{ return nIndex | nSemaphoreSaltBit; }
	static uint32 SemaphoreHandleToIndex ( TSemaphoreHandle handle )		{ return handle & ~nSemaphoreSaltBit; }
	
} // namespace Detail
} // namespace JobManager


///////////////////////////////////////////////////////////////////////////////
JobManager::CJobManager* JobManager::CJobManager::Instance()
{
	static JobManager::CJobManager _singleton;
	return &_singleton;
}

extern "C" 
{
	JobManager::IJobManager* GetJobManSPUInterface()
	{
		return JobManager::CJobManager::Instance();
	}
}

JobManager::CJobManager::CJobManager() 
	:	m_Initialized(false),
		m_pSPUBackEnd(NULL),
		m_pFallBackBackEnd(NULL),
		m_pThreadBackEnd(NULL),
		m_pBlockingBackEnd(NULL),
		m_nJobSystemEnabled(1),
		m_nJobsRunCounter(0),
		m_nFallbackJobsRunCounter(0)
{			






	m_FreeFunc = 0;
	m_MallocFunc = 0;

	// create backends	




#if !defined(PS3) // for PS3, only create the thread back end on demand, to spare 600kb of memory
	m_pThreadBackEnd = new ThreadBackEnd::CThreadBackEnd();
#endif




	CCpuFeatures *pCPU = new CCpuFeatures;
	pCPU->Detect();	
	m_nRegularWorkerThreads = pCPU->GetLogicalCPUCount();
	delete pCPU;


	m_pRegularWorkerFallbacks = new JobManager::SInfoBlock*[m_nRegularWorkerThreads];
	memset(m_pRegularWorkerFallbacks,0,sizeof(JobManager::SInfoBlock*) * m_nRegularWorkerThreads );

	m_pBlockingBackEnd = new BlockingBackEnd::CBlockingBackEnd(m_pRegularWorkerFallbacks, m_nRegularWorkerThreads);
	m_pFallBackBackEnd = new FallBackBackEnd::CFallBackBackEnd();
	
#if defined(JOBMANAGER_SUPPORT_PROFILING)
	nFrameIdx = 0;
#endif

	memset(m_arrJobInvokers,0,sizeof(m_arrJobInvokers));
	m_nJobInvokerIdx = 0;

	// init fallback backend early to be able to handle jobs before jobmanager is initialized
	if(m_pFallBackBackEnd)	m_pFallBackBackEnd->Init(-1 /*not used for fallback*/);
}

const bool JobManager::CJobManager::WaitForJob( JobManager::SJobState& rJobState, const int cTimeOutMS) const
{
#if defined(JOBMANAGER_SUPPORT_PROFILING)	
	rJobState.LockProfilingData();
	bool bSetWaitState = false;
	IF( rJobState.pJobProfilingData != NULL && rJobState.pJobProfilingData->nWaitBegin.GetValue() == 0, 1)
	{
		bSetWaitState = true;
		rJobState.pJobProfilingData->nWaitBegin = gEnv->pTimer->GetAsyncTime();	
		rJobState.pJobProfilingData->nThreadId = CryGetCurrentThreadId();
	}
	rJobState.UnLockProfilingData();
#endif

	rJobState.syncVar.Wait();

#if defined(JOBMANAGER_SUPPORT_PROFILING)	
	rJobState.LockProfilingData();
	IF( bSetWaitState && rJobState.pJobProfilingData, 1)
	{
		rJobState.pJobProfilingData->nWaitEnd = gEnv->pTimer->GetAsyncTime();	
		rJobState.pJobProfilingData = NULL;
	}
	rJobState.UnLockProfilingData();
#endif

	return true;
}

class ColorGenerator
{
public:
	ColorGenerator()
	{
		baseColors[0] = ColorB(1, 0, 0 );
		baseColors[1] = ColorB(0, 1, 0 );
		baseColors[2] = ColorB(0, 0, 1 );
		baseColors[3] = ColorB(1, 1, 0 );
		baseColors[4] = ColorB(0, 1, 1 );
		baseColors[5] = ColorB(1, 0, 1 );
		baseColors[6] = ColorB(1, 1, 1 );
		m_nNumGeneratedColor = 0;
	}

	ColorB GenerateColor()
	{
		int innerCycleNr = m_nNumGeneratedColor % 7;
		int outerCycleNr = m_nNumGeneratedColor / 7;

		int nFactor = 255;
		int n = nFactor;
		
		// lazy algorithm , should be automatic (but this ont produces enough colors for now)
		switch(outerCycleNr)
		{
		case 0: break;

		case 1: n = n - nFactor/2; break;

		case 2: n = n - nFactor/2; n = n + nFactor/4; break;
		case 3: n = n - nFactor/2; n = n - nFactor/4; break;

		case 4: n = n - nFactor/2; n = n + nFactor/4; n = n - nFactor/8; break;	
		case 5: n = n - nFactor/2; n = n + nFactor/4; n = n + nFactor/8; break;	
		case 6: n = n - nFactor/2; n = n - nFactor/4; n = n + nFactor/8; break;	
		case 7: n = n - nFactor/2; n = n - nFactor/4; n = n - nFactor/8; break;	

		case 8: n = n - nFactor/2; n = n + nFactor/4; n = n - n/2; n = n - nFactor/8; break;	
		case 9: n = n - nFactor/2; n = n + nFactor/4; n = n + n/2; n = n - nFactor/8;break;	
		case 10: n = n - nFactor/2; n = n - nFactor/4; n = n + n/2; n = n - nFactor/8;break;	
		case 11: n = n - nFactor/2; n = n - nFactor/4; n = n - n/2; n = n - nFactor/8;break;	

		case 12: n = n - nFactor/2; n = n + nFactor/4; n = n - n/2; n = n + nFactor/8;break;	
		case 13: n = n - nFactor/2; n = n + nFactor/4; n = n + n/2; n = n + nFactor/8;break;	
		case 14: n = n - nFactor/2; n = n - nFactor/4; n = n + n/2; n = n + nFactor/8;break;	
		case 15: n = n - nFactor/2; n = n - nFactor/4; n = n - n/2; n = n + nFactor/8;break;	

		default:
			CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "Color Generator run out of valid colors");
			assert(0);
		};

		ColorB newColor = baseColors[innerCycleNr];
		newColor.r *= n;
		newColor.g *= n;
		newColor.b *= n;

		m_nNumGeneratedColor++;				
		return newColor;

	}
private:
	ColorB baseColors[7];
	uint32 m_nNumGeneratedColor;
};
static ColorGenerator colorGen;

const JobManager::TJobHandle JobManager::CJobManager::GetJobHandle(const char* cpJobName, const uint32 cStrLen, JobManager::Invoker pInvoker)
{	
	ScopedSwitchToGlobalHeap heapSwitch; 
	static JobManager::SJobStringHandle cFailedLookup = {"", 0, JobManager::INVALID_JOB_HANDLE};	
	JobManager::SJobStringHandle cLookup = {cpJobName, cStrLen, JobManager::INVALID_JOB_HANDLE};

	if(m_pSPUBackEnd && m_pSPUBackEnd->HasValidSPURepository())
	{










	}

	// mis-use the JobQueue lock, this shouldn't create contention,
	// since this functions is only called once per job
	AUTO_LOCK(m_JobManagerLock);

	// don't insert in list when we only look up the job for debugging settings
	if(pInvoker == NULL)
	{
		std::set<JobManager::SJobStringHandle>::iterator it = m_registeredJobs.find(cLookup);
		return it == m_registeredJobs.end() ? &cFailedLookup : (JobManager::TJobHandle)&(*(it));
	}

	std::pair<std::set<JobManager::SJobStringHandle>::iterator,bool> it = m_registeredJobs.insert(cLookup);
	JobManager::TJobHandle ret = (JobManager::TJobHandle)&(*(it.first));

	// generate color for each entry
	if(it.second)
	{		
#if defined(JOBMANAGER_SUPPORT_PROFILING	)
		m_JobColors[cLookup] = colorGen.GenerateColor();
#endif		
		m_arrJobInvokers[m_nJobInvokerIdx] = pInvoker;
		ret->nJobInvokerIdx = m_nJobInvokerIdx;
		m_nJobInvokerIdx += 1;
		assert(m_nJobInvokerIdx < sizeof(m_arrJobInvokers)/sizeof(m_arrJobInvokers[0]));
	}	

	return ret;
}

const JobManager::EAddJobRes JobManager::CJobManager::AddJob( JobManager::CJobDelegator& RESTRICT_REFERENCE crJob, const uint32 cOpMode, const JobManager::TJobHandle cJobHandle )
{
	JobManager::SInfoBlock infoBlock;
	
	// Test if the job should be invoked
	bool bUseJobSystem = m_nJobSystemEnabled ? CJobManager::InvokeAsJob(cJobHandle) : false;
	
	// get producer/consumer queue settings
	const void *cpQueue		= crJob.GetQueue();
	const bool cNoQueue		= (cpQueue == NULL);
	const bool cKeepCache	= crJob.KeepCache();

	const uint32 cOrigParamSize 			= crJob.GetParamDataSize();
	const uint8 cParamSize					= cOrigParamSize >> 4;
	
	//reset info block	
	const int cQueueAddr						= (int)cpQueue;	
	unsigned int flagSet						= cNoQueue ? 0 : (unsigned int)JobManager::SInfoBlock::scHasQueue;	
	const uint32 cKeepCacheMask			= (unsigned int)(((int)(-(int)cKeepCache)) >> 31);

	infoBlock.eaExtJobStateAddress	= (SPU_DRIVER_INT_PTR)cpQueue;
	infoBlock.nflags								= (unsigned char)(flagSet | (cKeepCache ? JobManager::SInfoBlock::scKeepQueueCache : 0 ));		
	infoBlock.paramSize							= cParamSize;		
	infoBlock.jobInvoker						= crJob.GetGenericDelegator();
	infoBlock.SetOpMode(cOpMode);
	infoBlock.SetDedicatedThreadOnly( crJob.IsDedicatedThreadOnly() );

	const INT_PTR cExtJobAddr = (INT_PTR)crJob.GetJobState();
	if(cNoQueue && cExtJobAddr != 0)
	{
		infoBlock.SetExtJobStateAddress(cExtJobAddr);
		crJob.SetRunning();
	}

#if defined(JOBMANAGER_SUPPORT_PROFILING)		
	if(crJob.GetJobState() )
	{
		crJob.GetJobState()->LockProfilingData();
		if( crJob.GetJobState()->pJobProfilingData)
			crJob.GetJobState()->pJobProfilingData->jobHandle = cJobHandle;
		crJob.GetJobState()->UnLockProfilingData();

		PIXSetMarker(0, "Add Job: %s", cJobHandle->cpString);
	}
#endif
		
	IF( crJob.IsBlocking() == false && (bUseJobSystem == false || m_Initialized == false), 0 )
	{
		return static_cast<FallBackBackEnd::CFallBackBackEnd*>(m_pFallBackBackEnd)->FallBackBackEnd::CFallBackBackEnd::AddJob(crJob,cOpMode,cJobHandle, infoBlock);
	}


	// dispatch to the right BackEnd

	JobManager::EAddJobRes returnValue = eAJR_UnknownError;




	IF( m_pBlockingBackEnd && crJob.IsBlocking() && returnValue!=eAJR_Success,0)
	{
		returnValue = static_cast<BlockingBackEnd::CBlockingBackEnd*>(	m_pBlockingBackEnd )->BlockingBackEnd::CBlockingBackEnd::AddJob(crJob,cOpMode,cJobHandle, infoBlock);	
	}

	IF( returnValue!=eAJR_Success ,0)
	{











		if(m_pThreadBackEnd)
			returnValue = static_cast<ThreadBackEnd::CThreadBackEnd*>(	m_pThreadBackEnd )->ThreadBackEnd::CThreadBackEnd::AddJob(crJob,cOpMode,cJobHandle, infoBlock);	
	}

	IF( returnValue!=eAJR_Success,0)
	{		
		returnValue = static_cast<FallBackBackEnd::CFallBackBackEnd*>(m_pFallBackBackEnd)->FallBackBackEnd::CFallBackBackEnd::AddJob(crJob,cOpMode,cJobHandle, infoBlock);
	}
	
	return returnValue;
}

void JobManager::CJobManager::ShutDown()
{
#if defined(JOBMANAGER_SUPPORT_PROFILING)	
	ScopedSwitchToGlobalHeap heapSwitch; 
	for (int i=0; i<nCapturedFrames; ++i)
		for (size_t j=0; j<m_ProfilingData[i].size(); ++j)
			delete m_ProfilingData[i][j];
#endif 



	if (m_pFallBackBackEnd) m_pFallBackBackEnd->ShutDown();
	if (m_pThreadBackEnd) m_pThreadBackEnd->ShutDown(); 	
	if (m_pBlockingBackEnd) m_pBlockingBackEnd->ShutDown(); 	
}

void JobManager::CJobManager::Init( JobManager::TSPUFreeFunc FreeFunc, JobManager::TSPUMallocFunc MallocFunc, bool bEnablePrintf, uint32 nSysMaxWorker)
{





	// only init once
	if(m_Initialized) 
		return; 

	m_FreeFunc		= FreeFunc;
	m_MallocFunc	= MallocFunc;
	m_Initialized = true;

	// initialize the backends for this platform	
	if(m_pSPUBackEnd)				m_pSPUBackEnd->Init(nSysMaxWorker);		
#if !defined(PS3)
	if(m_pThreadBackEnd)
	{
		if( !m_pThreadBackEnd->Init(nSysMaxWorker) )
		{
			delete m_pThreadBackEnd;
			m_pThreadBackEnd = NULL;
		}
	}
	if(m_pBlockingBackEnd)		m_pBlockingBackEnd->Init(nSysMaxWorker);

#endif
}

bool JobManager::CJobManager::InvokeAsJob( const JobManager::TJobHandle cJobHandle ) const
{
	const char *cpJobName = cJobHandle->cpString;
	return this->CJobManager::InvokeAsJob(cpJobName);
}

bool JobManager::CJobManager::InvokeAsJob( const char *cpJobName ) const
{
#if defined(_RELEASE)
	return true; // no support for fallback interface in release
#endif
	
	// try to find the jobname in the job filter list
	IF( m_pJobFilter, 0)
	{
		if (const char* p = strstr(m_pJobFilter, cpJobName))
		if (p == m_pJobFilter || p[-1] == ',')
		{
			p += strlen(cpJobName);
			if (*p == 0 || *p == ',')
				return false;
		}
	}

	return m_nJobSystemEnabled != 0;
}

bool JobManager::CJobManager::InvokeAsSPUJob(const JobManager::TJobHandle cJobHandle) const
{




	return false;

}

bool JobManager::CJobManager::InvokeAsSPUJob( const char *cpJobName ) const
{



	return false;

}


uint32 JobManager::CJobManager::GetWorkerThreadId() const
{
	return JobManager::detail::GetWorkerThreadId();
}

JobManager::SJobProfilingData* JobManager::CJobManager::GetProfilingData()
{
#if defined(JOBMANAGER_SUPPORT_PROFILING	)
	ScopedSwitchToGlobalHeap heapSwitch; 
	AUTO_LOCK(m_JobManagerLock);
	int idx = GetFillFrameIdx();
	uint32 &rCounter = m_nProfilingDataCounter[idx];
	std::vector<SJobProfilingData*> &rProfilingData = m_ProfilingData[idx];

	if(rCounter >= rProfilingData.size())
		rProfilingData.push_back(NULL);

	if(rProfilingData[rCounter] == NULL )
	{
		rProfilingData[rCounter] = new SJobProfilingData();
	}
	JobManager::SJobProfilingData* pProfilingData = rProfilingData[rCounter];
	memset( pProfilingData, 0, sizeof(*pProfilingData));
		
	++rCounter;	
	return pProfilingData;
#else
	return NULL;
#endif
}

// struct to contain profling entries
struct SOrderedProfilingData
{
	SOrderedProfilingData( ColorB &rColor, int beg, int end) :
		color(rColor), nBegin(beg), nEnd(end){}

	ColorB color;		// color of the entry
	int nBegin;			// begin offset from graph
	int nEnd;				// end offset from graph
};

// free function to make the profiling rendering code more compact/readable
namespace DrawUtils
{
	void AddToGraph( ColorB *pGraph, const SOrderedProfilingData &rProfilingData )
	{
		for(int i = rProfilingData.nBegin ; i < rProfilingData.nEnd ; ++i )
		{
			pGraph[i] = rProfilingData.color;
		}		
	}

	
	void Draw2DBox( float fX, float fY, float fHeigth, float fWidth, const ColorB &rColor, float fScreenHeigth, float fScreenWidth, IRenderAuxGeom *pAuxRenderer)
	{		
		
		float fPosition[4][2] = {	{ fX,						fY },
															{ fX,						fY + fHeigth },
															{ fX + fWidth,	fY + fHeigth },
															{ fX + fWidth, fY} };

		// compute normalized position from absolute points
		Vec3 vPosition[4] = { Vec3(fPosition[0][0]/fScreenWidth, fPosition[0][1]/fScreenHeigth, 0.0f),
													Vec3(fPosition[1][0]/fScreenWidth, fPosition[1][1]/fScreenHeigth, 0.0f),
													Vec3(fPosition[2][0]/fScreenWidth, fPosition[2][1]/fScreenHeigth, 0.0f),
													Vec3(fPosition[3][0]/fScreenWidth, fPosition[3][1]/fScreenHeigth, 0.0f) };

		uint16 const anTriangleIndices[6] = { 0, 1, 2, 
																					0, 2, 3	};	

		pAuxRenderer->DrawTriangles( vPosition, 4, anTriangleIndices, 6, rColor );	
	}

	void Draw2DBoxOutLine( float fX, float fY, float fHeigth, float fWidth, const ColorB &rColor, float fScreenHeigth, float fScreenWidth, IRenderAuxGeom *pAuxRenderer)
	{		
		float fPosition[4][2] = {	{ fX - 1.0f,					fY - 1.0f},
															{ fX - 1.0f,					fY + fHeigth + 1.0f},
															{ fX + fWidth + 1.0f,	fY + fHeigth + 1.0f},
															{ fX + fWidth + 1.0f,	fY - 1.0f} };

		// compute normalized position from absolute points
		Vec3 vPosition[4] = { Vec3(fPosition[0][0]/fScreenWidth, fPosition[0][1]/fScreenHeigth, 0.0f),
													Vec3(fPosition[1][0]/fScreenWidth, fPosition[1][1]/fScreenHeigth, 0.0f),
													Vec3(fPosition[2][0]/fScreenWidth, fPosition[2][1]/fScreenHeigth, 0.0f),
													Vec3(fPosition[3][0]/fScreenWidth, fPosition[3][1]/fScreenHeigth, 0.0f) };


		pAuxRenderer->DrawLine(vPosition[0],rColor, vPosition[1], rColor);
		pAuxRenderer->DrawLine(vPosition[1],rColor, vPosition[2], rColor);
		pAuxRenderer->DrawLine(vPosition[2],rColor, vPosition[3], rColor);
		pAuxRenderer->DrawLine(vPosition[3],rColor, vPosition[0], rColor);
	}
		
	void DrawGraph( ColorB *pGraph, int nGraphSize, float fBaseX, float fbaseY, float fHeight, float fScreenHeigth, float fScreenWidth, IRenderAuxGeom *pAuxRenderer)
	{
		for( int i = 0 ; i < nGraphSize ;  )
		{
			// skip empty fields
			if(pGraph[i].r == 0 && pGraph[i].g == 0 && pGraph[i].b == 0)
			{
				++i;
				continue;
			}

			float fBegin = (float)i;
			ColorB color = pGraph[i];

			while(true)
			{
				if( (i+1 >= nGraphSize) ||			// reached end of graph
						(pGraph[i+1] != pGraph[i]))	// start of different region
				{
					// draw box for this graph part
					float fX = fBaseX + fBegin;
					float fY = fbaseY;
					float fEnd = (float)i;
					float fWidth = fEnd - fBegin; // compute width 	
					DrawUtils::Draw2DBox(fX,fY, fHeight,fWidth, pGraph[i],fScreenHeigth,fScreenWidth, pAuxRenderer);			
					++i;
					break;
				}

				++i; // still the same graph, go to next entry
			}			
		}
	}

	void WriteShortLabel( float fTextSideOffset, float fTopOffset, float fTextSize, float *fTextColor, char *tmpBuffer, int nCapChars )
	{
		char textBuffer[512] = {0};
		char *pDst = textBuffer;
		char *pEnd = textBuffer + nCapChars; // keep space for tailing '\0'
		char *pSrc = tmpBuffer;
		while(pSrc != '\0' && pDst < pEnd )
		{
			*pDst = *pSrc;
			++pDst; ++pSrc;
		}
		gEnv->pRenderer->Draw2dLabel( fTextSideOffset, fTopOffset, fTextSize, fTextColor, false, textBuffer );
	}

} // namespace DrawUtils


struct SJobProflingRenderData
{	
	const char		*pName;		// name of the job
	ColorB				color;		// color to use to represent this job

	CTimeValue dispatchTime;
	CTimeValue runTime;
	CTimeValue waitTime;

	CTimeValue cacheTime;
	CTimeValue codePagingTime;
	CTimeValue fnResolveTime;
	CTimeValue memtransferSyncTime;

	uint32 invocations;

	bool operator<(const SJobProflingRenderData &rOther ) const
	{
		return (INT_PTR)pName < (INT_PTR)rOther.pName;
	}

	bool operator<(const  char *pOther ) const
	{
		return (INT_PTR)pName < (INT_PTR)pOther;
	}
};

struct SJobProflingRenderDataCmp
{
	bool operator()(const SJobProflingRenderData &rA,const char *pB) const { return (INT_PTR)rA.pName < (INT_PTR)pB; }
	bool operator()(const SJobProflingRenderData &rA,const SJobProflingRenderData &rB) const { return (INT_PTR)rA.pName < (INT_PTR)rB.pName; }
	bool operator()(const char *pA,const SJobProflingRenderData &rB) const { return (INT_PTR)pA < (INT_PTR)rB.pName; }
};

struct SJobProfilingLexicalSort
{
	bool operator()( const SJobProflingRenderData &rA, const SJobProflingRenderData &rB ) const
	{
		return strcmp(rA.pName, rB.pName) < 0;
	}

};

struct SWorkerProfilingRenderData
{
	CTimeValue runTime;	
};

struct SRegionTime
{
	const char *pName;
	ColorB color;
	CTimeValue executionTime;

	bool operator<(const SRegionTime &rOther ) const
	{
		return (uint32)pName < (uint32)rOther.pName;
	}

	bool operator<(const  char *pOther ) const
	{
		return (uint32)pName < (uint32)pOther;
	}

	bool operator==(const SRegionTime &rOther ) const
	{
		return (uint32)pName == (uint32)rOther.pName;
	}
};

struct SRegionTimeCmp
{
	bool operator()(const SRegionTime &rA,const char *pB) const { return (INT_PTR)rA.pName < (INT_PTR)pB; }
	bool operator()(const SRegionTime &rA,const SRegionTime &rB) const { return (INT_PTR)rA.pName < (INT_PTR)rB.pName; }
	bool operator()(const char *pA,const SRegionTime &rB) const { return (INT_PTR)pA < (INT_PTR)rB.pName; }
};

struct SRegionTimeSorter
{
	bool operator()( const SRegionTime &rA, const SRegionTime &rB) const
	{
		// sort highest times first
		return rA.executionTime > rB.executionTime;
	}
};

struct SRegionLexicalSorter
{
	bool operator()( const SRegionTime &rA, const SRegionTime &rB) const
	{
		// sort highest times first
		return strcmp(rA.pName, rB.pName) < 0;		
	}
};

struct SThreadProflingRenderData
{
	CTimeValue executionTime;
	CTimeValue waitTime;
};

void JobManager::CJobManager::Update(int nJobSystemProfiler)
{
	ScopedSwitchToGlobalHeap heapSwitch; 	







	m_nJobsRunCounter = 0;
	m_nFallbackJobsRunCounter = 0;

	// profiler disabled
	if(nJobSystemProfiler == 0 )
		return;
	





#if defined(JOBMANAGER_SUPPORT_PROFILING)
	int nFrameId = GetRenderFrameIdx();

	CTimeValue frameStartTime = m_FrameStartTime[nFrameId];
	CTimeValue frameEndTime = m_FrameStartTime[GetPrevFrameIdx()];

	// skip first frames still we have enough data
	if( frameStartTime.GetValue() == 0 )
		return;

	gEnv->GetJobManager()->AddStartMarker("JobManager", eTI_JobManagerUpdate);

	// compute how long the displayed frame took
	CTimeValue diffTime = frameEndTime - frameStartTime;

	// get used thread ids
	uint32 nMainThreadId, nRenderThreadId;
	gEnv->pRenderer->GetThreadIDs(nMainThreadId,nRenderThreadId);

	// not compute the relative screen size, and how many pixels are represented by a time value
	int nScreenHeight = gEnv->pRenderer->GetHeight();
	int nScreenWidth = gEnv->pRenderer->GetWidth();

	float fScreenHeight = (float)nScreenHeight;
	float fScreenWidth = (float)nScreenWidth;
	
	float fGraphHeight = fScreenHeight * 0.03f; // 3%
	float fGraphWidth = fScreenWidth * 0.87f; // 87%
	
	float pixelPerTime = (float)fGraphWidth / diffTime.GetValue();
	



	const int nNumWorker = m_pThreadBackEnd->GetNumWorkerThreads();

	const int nNumJobs = m_registeredJobs.size();
	const int nGraphSize = (int)fGraphWidth;
	int nNumRegions = m_StartStopMarker[nFrameId].size();
	
	// allocate structure on the stack to prevent too many costly memory allocations
	//first structures to represent the graph data, we just use (0,0,0) as not set
	// and set each field with the needed color, then we render the resulting boxes
	// in one pass
	PREFAST_SUPPRESS_WARNING(6255)
	ColorB *arrMainThreadRegion = (ColorB *)alloca( nGraphSize *sizeof(ColorB) );
	memset(arrMainThreadRegion,0,nGraphSize *sizeof(ColorB));
	PREFAST_SUPPRESS_WARNING(6255)
	ColorB *arrMainThreadWaitRegion = (ColorB *)alloca( nGraphSize *sizeof(ColorB) );
	memset(arrMainThreadWaitRegion,0,nGraphSize *sizeof(ColorB));
	PREFAST_SUPPRESS_WARNING(6255)
	ColorB *arrRenderThreadRegion = (ColorB *)alloca( nGraphSize *sizeof(ColorB) );
	memset(arrRenderThreadRegion,0,nGraphSize *sizeof(ColorB));

	PREFAST_SUPPRESS_WARNING(6255)
	SRegionTime *arrRegionProfilingData = (SRegionTime*)alloca( nNumRegions * sizeof(SRegionTime));
	memset(arrRegionProfilingData,0, nNumRegions * sizeof(SRegionTime));

	int nRegionCounter = 0;
	for(std::vector<SMarker>::iterator it = m_StartStopMarker[nFrameId].begin() ; it != m_StartStopMarker[nFrameId].end() ; ++it )
	{		
		arrRegionProfilingData[nRegionCounter].pName = it->pName;
		arrRegionProfilingData[nRegionCounter].color = it->color;
		++nRegionCounter;
	}

	// remove duplicates of region entries
	std::sort( arrRegionProfilingData, arrRegionProfilingData + nNumRegions );
	SRegionTime *pEnd = std::unique( arrRegionProfilingData, arrRegionProfilingData + nNumRegions );
	nNumRegions = (int)(pEnd - arrRegionProfilingData);

	PREFAST_SUPPRESS_WARNING(6255)
	ColorB **arrWorkerThreadsRegions = (ColorB**)alloca(nNumWorker * sizeof(ColorB**));
	
	for( int i = 0 ; i < nNumWorker ; ++i )
	{
		PREFAST_SUPPRESS_WARNING(6263) PREFAST_SUPPRESS_WARNING(6255)
		arrWorkerThreadsRegions[i] = (ColorB *)alloca( nGraphSize *sizeof(ColorB) );
		memset(arrWorkerThreadsRegions[i],0,nGraphSize *sizeof(ColorB));
	}

	// allocate per worker data
	PREFAST_SUPPRESS_WARNING(6255)
	SWorkerProfilingRenderData *arrWorkerProfilingRenderData = (SWorkerProfilingRenderData*)alloca(nNumWorker*sizeof(SWorkerProfilingRenderData));
	memset(arrWorkerProfilingRenderData,0,nNumWorker*sizeof(SWorkerProfilingRenderData));

	// allocate per job informations
	PREFAST_SUPPRESS_WARNING(6255)
	SJobProflingRenderData *arrJobProfilingRenderData = (SJobProflingRenderData *)alloca(nNumJobs*sizeof(SJobProflingRenderData));
	memset(arrJobProfilingRenderData,0,nNumJobs*sizeof(SJobProflingRenderData));

	// init job data
	int nJobIndex = 0;
	for( std::set<JobManager::SJobStringHandle>::const_iterator it = m_registeredJobs.begin() ; it != m_registeredJobs.end() ; ++it )
	{
		arrJobProfilingRenderData[nJobIndex].pName = it->cpString;
		arrJobProfilingRenderData[nJobIndex].color = m_JobColors[*it];
		++nJobIndex;
	}

	std::sort( arrJobProfilingRenderData, arrJobProfilingRenderData + nNumJobs );


	CTimeValue waitTimeMainThread;
	CTimeValue waitTimeRenderThread;

	// get the uppder 32bit of the current rdtsc timer, these are combined with the SPU ones




	// ==== collect per job/thread times == //
	for( int j = 0 ; j < 2 ; ++j ) 
	{
		// select the right frame for the pass
		int nIdx = (j == 0 ? GetPrevRenderFrameIdx() : GetRenderFrameIdx() );
		for( uint32 i = 0 ; i < m_nProfilingDataCounter[nIdx] ; ++i )
		{
			SJobProfilingData profilingData = *m_ProfilingData[nIdx][i];

			// skip invalid entries
			IF(profilingData.jobHandle == NULL, 0)
				continue;

			// skip jobs which did never run
			IF( profilingData.nEndTime.GetValue() == 0 || profilingData.nStartTime.GetValue() == 0, 0)
				continue;

			// get the job profiling rendering data structure
			SJobProflingRenderData *pJobProfilingRenderingData = 
				std::lower_bound( arrJobProfilingRenderData, arrJobProfilingRenderData + nNumJobs, profilingData.jobHandle->cpString, SJobProflingRenderDataCmp() );














			// did part of the job run during this frame
			if(	(profilingData.nEndTime <= frameEndTime && profilingData.nStartTime >= frameStartTime) || // in this frame
					(profilingData.nEndTime >= frameStartTime && profilingData.nEndTime <= frameEndTime && profilingData.nStartTime < frameStartTime) || // from the last frame into this
					(profilingData.nStartTime >= frameStartTime && profilingData.nStartTime <= frameEndTime && profilingData.nEndTime > frameEndTime) ) // goes into the next frame				
			{			
				// clamp frame start/end into this frame
				profilingData.nEndTime = ( profilingData.nEndTime > frameEndTime ? frameEndTime : profilingData.nEndTime);
				profilingData.nStartTime = ( profilingData.nStartTime < frameStartTime ? frameStartTime : profilingData.nStartTime);

				// compute integer offset in the graph for start and stop position			
				CTimeValue startOffset = profilingData.nStartTime - frameStartTime;
				CTimeValue endOffset = profilingData.nEndTime - frameStartTime;

				int nGraphOffsetStart = (int)(startOffset.GetValue() * pixelPerTime);
				int nGraphOffsetEnd = (int)(endOffset.GetValue() * pixelPerTime);

				// get the correct worker id 
				uint32 nWorkerIdx = profilingData.nWorkerThread;
				//clean out a possible SPU marker
				nWorkerIdx &= ~0x80000000U;

				// accumulate the time spend in dispatch(time the job waited to run)
				pJobProfilingRenderingData->dispatchTime += (profilingData.nStartTime - profilingData.nDispatchTime);
				pJobProfilingRenderingData->runTime += (profilingData.nEndTime - profilingData.nStartTime);
				
				// collect PS3/SPU only information about sw-cache and code paging











				// count job invocations
				pJobProfilingRenderingData->invocations += 1;

				// accumulate time per worker thread
				arrWorkerProfilingRenderData[nWorkerIdx].runTime += (profilingData.nEndTime - profilingData.nStartTime);

				DrawUtils::AddToGraph( arrWorkerThreadsRegions[nWorkerIdx], SOrderedProfilingData(pJobProfilingRenderingData->color,nGraphOffsetStart,nGraphOffsetEnd) );
			}


			// did this job have wait time in this frame
			if(	((profilingData.nWaitEnd.GetValue() - profilingData.nWaitBegin.GetValue()) > 1 ) &&
					(	(profilingData.nWaitEnd <= frameEndTime && profilingData.nWaitBegin >= frameStartTime) || // in this frame
						(profilingData.nWaitEnd >= frameStartTime && profilingData.nWaitEnd <= frameEndTime && profilingData.nWaitBegin < frameStartTime) || // from the last frame into this
						(profilingData.nWaitBegin >= frameStartTime && profilingData.nWaitBegin <= frameEndTime && profilingData.nWaitEnd > frameEndTime) )) // goes into the next frame										
			{
				// clamp frame start/end into this frame
				profilingData.nWaitEnd = ( profilingData.nWaitEnd > frameEndTime ? frameEndTime : profilingData.nWaitEnd);
				profilingData.nWaitBegin = ( profilingData.nWaitBegin < frameStartTime ? frameStartTime : profilingData.nWaitBegin);

				// accumulate wait time
				pJobProfilingRenderingData->waitTime += (profilingData.nWaitEnd - profilingData.nWaitBegin);	

				// compute graph offsets
				CTimeValue startOffset = profilingData.nWaitBegin - frameStartTime;
				CTimeValue endOffset = profilingData.nWaitEnd - frameStartTime;

				int nGraphOffsetStart = (int)(startOffset.GetValue() * pixelPerTime);
				int nGraphOffsetEnd = (int)(endOffset.GetValue() * pixelPerTime);

				// add to the right thread(we care only for main and renderthread)
				if( profilingData.nThreadId == nMainThreadId )
				{
					DrawUtils::AddToGraph( arrMainThreadWaitRegion, SOrderedProfilingData(pJobProfilingRenderingData->color,nGraphOffsetStart,nGraphOffsetEnd) );
					waitTimeMainThread += (profilingData.nWaitEnd - profilingData.nWaitBegin);
				}
				else if(profilingData.nThreadId == nRenderThreadId )
				{
					waitTimeRenderThread += (profilingData.nWaitEnd - profilingData.nWaitBegin);
					DrawUtils::AddToGraph( arrRenderThreadRegion, SOrderedProfilingData(pJobProfilingRenderingData->color,nGraphOffsetStart,nGraphOffsetEnd) );
				}
			}
		}
	}
			

	// ==== collect mainthread regions ==== //
	for(std::vector<SMarker>::iterator it = m_StartStopMarker[nFrameId].begin() ; it != m_StartStopMarker[nFrameId].end() ; ++it )
	{		
		// compute integer offset in the graph for start and stop position			
		CTimeValue startOffset = it->startTime - frameStartTime;
		CTimeValue endOffset = it->endTime - frameStartTime;

		int GraphOffsetStart = (int)(startOffset.GetValue() * pixelPerTime);
		int GraphOffsetEnd = (int)(endOffset.GetValue() * pixelPerTime);
		DrawUtils::AddToGraph( arrMainThreadRegion, SOrderedProfilingData(it->color,GraphOffsetStart,GraphOffsetEnd) );				

		SRegionTime *pRegionProfilingData = 
			std::lower_bound( arrRegionProfilingData, arrRegionProfilingData + nNumRegions, it->pName, SRegionTimeCmp() );

		pRegionProfilingData->executionTime += (it->endTime - it->startTime);
	}


	// ==== begin rendering of profiling data ==== //
	// render profiling data
	IRenderAuxGeom* pAuxGeomRenderer			= gEnv->pRenderer->GetIRenderAuxGeom();
	SAuxGeomRenderFlags const oOldFlags = pAuxGeomRenderer->GetRenderFlags();

	SAuxGeomRenderFlags oFlags( e_Def2DPublicRenderflags );
	oFlags.SetDepthTestFlag( e_DepthTestOff );
	oFlags.SetDepthWriteFlag( e_DepthWriteOff );
	oFlags.SetCullMode( e_CullModeNone );
	oFlags.SetAlphaBlendMode( e_AlphaNone );
	pAuxGeomRenderer->SetRenderFlags( oFlags );

	float fTopOffset = fScreenHeight * 0.05f; // start near the top
	float fGraphSideOffset = fScreenWidth *0.10f; // keep enough space for the text labels on the left side
	float fTextSideOffset = fScreenWidth *0.01f; 
	float fTextSize = 1.4f;
	float fTextHeigthOffset = fScreenHeight * 0.019f; // is there a better way than this guessing?
	float fTextHeigthSpacing = 2.0f; // is there a better way than this guessing?
	float fTextWidth = fScreenWidth * 0.20f;
	float fTextWidthSpacing = fScreenWidth * 0.05f;
	float fJobTextWidth = fScreenWidth * 0.36f;

	ColorB boxBorderColor(128,128,128,0);
	float fTextColor[] = {1.0f,1.0f,1.0f,1.0f};

	// == main thread == //
	// draw main thread box and label
	DrawUtils::Draw2DBoxOutLine( fGraphSideOffset, fTopOffset, fGraphHeight, fGraphWidth, boxBorderColor, fScreenHeight, fScreenWidth, pAuxGeomRenderer );
	gEnv->pRenderer->Draw2dLabel( fTextSideOffset, fTopOffset, fTextSize, fTextColor, false, "MainThread" );

	// draw main thread regions
	DrawUtils::DrawGraph( arrMainThreadRegion, nGraphSize, fGraphSideOffset, fTopOffset, fGraphHeight/2.0f, fScreenHeight, fScreenWidth, pAuxGeomRenderer );
				
	// draw were the main thread has waited
	DrawUtils::DrawGraph( arrMainThreadWaitRegion, nGraphSize, fGraphSideOffset, fTopOffset + fGraphHeight/2.0f, fGraphHeight/2.0f, fScreenHeight, fScreenWidth, pAuxGeomRenderer );
	

	// == render thread == //
	fTopOffset += fGraphHeight/2 + fGraphHeight;

	// draw render thread box and label
	DrawUtils::Draw2DBoxOutLine( fGraphSideOffset, fTopOffset, fGraphHeight, fGraphWidth, boxBorderColor, fScreenHeight, fScreenWidth, pAuxGeomRenderer );
	gEnv->pRenderer->Draw2dLabel( fTextSideOffset, fTopOffset, fTextSize, fTextColor, false, "RenderThread");

	// draw render thread wait times
	DrawUtils::DrawGraph( arrRenderThreadRegion, nGraphSize, fGraphSideOffset,fTopOffset, fGraphHeight, fScreenHeight, fScreenWidth, pAuxGeomRenderer );

	// == worker threads == //
	fTopOffset += 2.0f *fGraphHeight; // add a little bit more spacing between mainthreads and worker
	for( int i = 0 ; i < nNumWorker ; ++i )
	{
		// draw worker box and label
		char workerThreadName[128] = {0};
		sprintf(workerThreadName, "Worker %d", i);
		DrawUtils::Draw2DBoxOutLine( fGraphSideOffset, fTopOffset, fGraphHeight, fGraphWidth, boxBorderColor, fScreenHeight, fScreenWidth, pAuxGeomRenderer );
		gEnv->pRenderer->Draw2dLabel( fTextSideOffset, fTopOffset, fTextSize, fTextColor, false, workerThreadName);

		// draw render thread wait times
		DrawUtils::DrawGraph( arrWorkerThreadsRegions[i], nGraphSize, fGraphSideOffset,fTopOffset, fGraphHeight, fScreenHeight, fScreenWidth, pAuxGeomRenderer );

		fTopOffset += fGraphHeight + fGraphHeight/2;
	}

	// are we only interrested in the graph and not in the values?
	if(nJobSystemProfiler == 2 )
	{	
		// Restore Aux Render setup
		gEnv->GetJobManager()->AddStopMarker(eTI_JobManagerUpdate);
		pAuxGeomRenderer->SetRenderFlags( oOldFlags );
		return;
	}

	// == draw info boxes == //	
	char tmpBuffer[512] = {0};
	float fInfoBoxTextOffset = fTopOffset;

	sprintf(tmpBuffer, "MainThread %05.2f ms", (frameEndTime - frameStartTime).GetMilliSeconds());
	DrawUtils::WriteShortLabel( fTextSideOffset, fInfoBoxTextOffset, fTextSize, fTextColor, tmpBuffer, 27 );

	// sort regions by time
	std::sort(arrRegionProfilingData, arrRegionProfilingData + nNumRegions, SRegionLexicalSorter() );

	fInfoBoxTextOffset += fTextHeigthOffset;
	for( int i = 0 ; i < nNumRegions ; ++i )
	{
		sprintf(tmpBuffer, "  %s %05.2f ms", arrRegionProfilingData[i].pName, arrRegionProfilingData[i].executionTime.GetMilliSeconds() );
		DrawUtils::WriteShortLabel( fTextSideOffset, fInfoBoxTextOffset, fTextSize, fTextColor, tmpBuffer, 27 );				
		DrawUtils::Draw2DBox(fTextSideOffset, fInfoBoxTextOffset+2.0f, fTextHeigthOffset, fTextWidth, arrRegionProfilingData[i].color,fScreenHeight, fScreenWidth, pAuxGeomRenderer);
					
		fInfoBoxTextOffset += fTextHeigthOffset + fTextHeigthSpacing;
	}

	// draw worker data
	fInfoBoxTextOffset += 2.0f * fTextHeigthOffset;
	DrawUtils::WriteShortLabel( fTextSideOffset, fInfoBoxTextOffset, fTextSize, fTextColor, "Workers", 27 );

	fInfoBoxTextOffset += fTextHeigthOffset;
	CTimeValue accumulatedWorkerTime;
	for( int i = 0 ; i < nNumWorker ; ++i )
	{
		float runTimePercent = 100.0f/(frameEndTime - frameStartTime).GetValue()*arrWorkerProfilingRenderData[i].runTime.GetValue();
		sprintf(tmpBuffer, "  Worker %d: %05.2f ms %04.1f p", i, arrWorkerProfilingRenderData[i].runTime.GetMilliSeconds(),runTimePercent );
		DrawUtils::WriteShortLabel( fTextSideOffset, fInfoBoxTextOffset, fTextSize, fTextColor, tmpBuffer, 27 );		
		fInfoBoxTextOffset += fTextHeigthOffset;

		// accumulate times for all worker
		accumulatedWorkerTime += arrWorkerProfilingRenderData[i].runTime;
	}
	
	// draw accumulated worker time and percentage
	sprintf(tmpBuffer, "-------------------------------");
	DrawUtils::WriteShortLabel( fTextSideOffset, fInfoBoxTextOffset, fTextSize, fTextColor, tmpBuffer, 27 );		
	fInfoBoxTextOffset += fTextHeigthOffset;
	float accRunTimePercentage = 100.0f/((frameEndTime - frameStartTime).GetValue() * nNumWorker)*accumulatedWorkerTime.GetValue();
	sprintf(tmpBuffer, "Sum: %05.2f ms %04.1f p", accumulatedWorkerTime.GetMilliSeconds(),accRunTimePercentage );
	DrawUtils::WriteShortLabel( fTextSideOffset, fInfoBoxTextOffset, fTextSize, fTextColor, tmpBuffer, 27 );		
	fInfoBoxTextOffset += fTextHeigthOffset;

	// draw accumulated wait times of main and renderthread
	fInfoBoxTextOffset += 2.0f * fTextHeigthOffset;
	sprintf(tmpBuffer, "MainThread Wait %05.2f ms", waitTimeMainThread.GetMilliSeconds() );
	DrawUtils::WriteShortLabel( fTextSideOffset, fInfoBoxTextOffset, fTextSize, fTextColor, tmpBuffer, 27 );	
	fInfoBoxTextOffset += fTextHeigthOffset;
	sprintf(tmpBuffer, "RenderThread Wait %05.2f ms", waitTimeRenderThread.GetMilliSeconds() );
	DrawUtils::WriteShortLabel( fTextSideOffset, fInfoBoxTextOffset, fTextSize, fTextColor, tmpBuffer, 27 );		


	// == render per job informations == //
	float fJobInfoBoxTextOffset = fTopOffset;
	float fJobInfoBoxSideOffset = fScreenWidth * 0.16f + fTextSideOffset + fTextWidthSpacing;
	float fJobTextSize = 1.2f;
	float fJobInfoBoxSpacing = fScreenWidth * 0.03f;

	// sort jobs by their name
	std::sort( arrJobProfilingRenderData, arrJobProfilingRenderData + nNumJobs, SJobProfilingLexicalSort() );
	
	bool bHasSPUs = m_pSPUBackEnd != NULL;
	sprintf(tmpBuffer, "JobName (Num Invocations)  TimeExecuted(MS)  TimeWait(MS)  SW-CacheTime(MS) CodePagingTime(MS)  MemtransferSyncTime(MS)");
	DrawUtils::WriteShortLabel( fJobInfoBoxSideOffset, fJobInfoBoxTextOffset, fJobTextSize, fTextColor, tmpBuffer, 160 );	
	fJobInfoBoxTextOffset += 1.4f * fTextHeigthOffset;

	for( int i = 0 ; i < nNumJobs ; ++i )
	{
		// filter jobs we are not interested in
		if( strstr(arrJobProfilingRenderData[i].pName, "DXPSThreadSPU") )
			continue;
		
		memset(tmpBuffer,0,sizeof(tmpBuffer));		
		SJobProflingRenderData &rJobProfilingData = arrJobProfilingRenderData[i];
		int nOffset = sprintf(tmpBuffer, "%s", rJobProfilingData.pName );
		tmpBuffer[20] = '\0'; // remove the tailing '\0' from the first sprintf
	
		char tempBuffer2[512] = {0};
		sprintf(tempBuffer2," (%3d):  %5.2f  %5.2f %5.2f %5.2f %5.2f", rJobProfilingData.invocations,
			rJobProfilingData.runTime.GetMilliSeconds(), rJobProfilingData.waitTime.GetMilliSeconds(),
			rJobProfilingData.cacheTime.GetMilliSeconds(), rJobProfilingData.codePagingTime.GetMilliSeconds(), rJobProfilingData.memtransferSyncTime.GetMilliSeconds() );
		
		// do we need to restart a new colum
		if( fJobInfoBoxTextOffset + (fJobTextSize * fTextHeigthOffset) > (fScreenHeight*0.99f) )
		{
			fJobInfoBoxTextOffset = fTopOffset + (1.4f * fTextHeigthOffset);
			fJobInfoBoxSideOffset += fJobTextWidth + (fTextWidthSpacing * 0.3f);
		}		

		DrawUtils::WriteShortLabel( fJobInfoBoxSideOffset, fJobInfoBoxTextOffset, fJobTextSize, fTextColor, tmpBuffer, 20 );		
		DrawUtils::WriteShortLabel( fJobInfoBoxSideOffset + fScreenWidth * 0.13f, fJobInfoBoxTextOffset , fJobTextSize, fTextColor, tempBuffer2, 40 );		
		DrawUtils::Draw2DBox(fJobInfoBoxSideOffset, fJobInfoBoxTextOffset, fTextHeigthOffset, fJobTextWidth, rJobProfilingData.color,fScreenHeight, fScreenWidth, pAuxGeomRenderer);	
		fJobInfoBoxTextOffset += 1.3f * fTextHeigthOffset;		

	}

	// Restore Aux Render setup
	pAuxGeomRenderer->SetRenderFlags( oOldFlags );

	gEnv->GetJobManager()->AddStopMarker(eTI_JobManagerUpdate);
#endif
	
}

void JobManager::CJobManager::SetFrameStartTime( const CTimeValue &rFrameStartTime )
{
#if defined(JOBMANAGER_SUPPORT_PROFILING)	
	ScopedSwitchToGlobalHeap heapSwitch; 
	++nFrameIdx;
	int idx = GetFillFrameIdx();
	m_FrameStartTime[idx] = rFrameStartTime;	
	// reset profiling counter
	m_nProfilingDataCounter[idx] = 0;

	// clear marker
	m_StartStopMarkerStack[idx].clear();
	m_StartStopMarker[idx].clear();
#endif

}

void JobManager::CJobManager::AddStartMarker( const char *pName,unsigned int id )
{
#if defined(JOBMANAGER_SUPPORT_PROFILING)	
	ScopedSwitchToGlobalHeap heapSwitch; 
	int idx = GetFillFrameIdx();
	SMarker marker = { pName, GetRegionColor(id), id, gEnv->pTimer->GetAsyncTime(), CTimeValue() };
	m_StartStopMarkerStack[idx].push_back(marker);

#endif
}

void JobManager::CJobManager::AddStopMarker( unsigned int id )
{
#if defined(JOBMANAGER_SUPPORT_PROFILING)
	ScopedSwitchToGlobalHeap heapSwitch; 
	int idx = GetFillFrameIdx();
	
	if( m_StartStopMarkerStack[idx].empty() )
		return;

	assert( m_StartStopMarkerStack[idx].back().id == id );

	// pop from stack
	SMarker marker = m_StartStopMarkerStack[idx].back();
	m_StartStopMarkerStack[idx].pop_back();

	marker.endTime = gEnv->pTimer->GetAsyncTime();

	// prevent a crash due physics fiber switching
	if( m_StartStopMarker[idx].size() > 1000)
		return;

	m_StartStopMarker[idx].push_back(marker);
#endif
}

ColorB JobManager::CJobManager::GetRegionColor( unsigned int id )
{
#if defined(JOBMANAGER_SUPPORT_PROFILING)	
	ScopedSwitchToGlobalHeap heapSwitch; 
	if( m_RegionColors.find(id) == m_RegionColors.end() )
	{
		m_RegionColors[id] = colorGen.GenerateColor();
	}
	return m_RegionColors[id];
#else
	return ColorB();
#endif
}


///////////////////////////////////////////////////////////////////////////////
JobManager::TSemaphoreHandle JobManager::CJobManager::AllocateSemaphore(volatile const void *pOwner)
{
	// static checks
	STATIC_CHECK( sizeof(JobManager::TSemaphoreHandle) == 2, ERROR_SIZE_OF_SEMAPHORE_HANDLE_IS_NOT_2 );
	STATIC_CHECK( static_cast<int>(nSemaphorePoolSize) < JobManager::Detail::nSemaphoreSaltBit, ERROR_SEMAPHORE_POOL_IS_BIGGER_THAN_SALT_HANDLE );
	STATIC_CHECK( IsPowerOfTwoCompileTime<nSemaphorePoolSize>::IsPowerOfTwo, ERROR_SEMAPHORE_SIZE_IS_NOT_POWER_OF_TWO );

	AUTO_LOCK(m_JobManagerLock);
	int nSpinCount = 0;
	for(;;) // normally we should never spin here, if we do, increase the semaphore pool size
	{
		if(nSpinCount > 10 )
			__debugbreak(); // breaking here means that there is a logic flaw which causes not finished syncvars to be returned to the pool

		uint32 nIndex = (++m_nCurrentSemaphoreIndex) % nSemaphorePoolSize;
		SJobFinishedConditionVariable *pSemaphore = &m_JobSemaphorePool[nIndex];
		if(pSemaphore->HasOwner())
		{
			nSpinCount++;
			continue;
		}

		// set owner and increase ref counter
		pSemaphore->SetRunning();
		pSemaphore->SetOwner( pOwner);
		pSemaphore->AddRef( pOwner );
		return JobManager::Detail::IndexToSemaphoreHandle(nIndex);
	}
}

///////////////////////////////////////////////////////////////////////////////
void JobManager::CJobManager::DeallocateSemaphore( JobManager::TSemaphoreHandle nSemaphoreHandle, volatile const void *pOwner)
{
	AUTO_LOCK(m_JobManagerLock);
	uint32 nIndex = JobManager::Detail::SemaphoreHandleToIndex(nSemaphoreHandle);
	assert(nIndex<nSemaphorePoolSize);

	SJobFinishedConditionVariable *pSemaphore = &m_JobSemaphorePool[nIndex];

	if(pSemaphore->DecRef( pOwner ) == 0)
	{
		if( pSemaphore->IsRunning() )
			__debugbreak();
		pSemaphore->ClearOwner(pOwner);		
	}
}

///////////////////////////////////////////////////////////////////////////////
bool JobManager::CJobManager::AddRefSemaphore( JobManager::TSemaphoreHandle nSemaphoreHandle, volatile const void *pOwner )
{
	AUTO_LOCK(m_JobManagerLock);
	uint32 nIndex = JobManager::Detail::SemaphoreHandleToIndex(nSemaphoreHandle);
	assert(nIndex<nSemaphorePoolSize);

	SJobFinishedConditionVariable *pSemaphore = &m_JobSemaphorePool[nIndex];

	return pSemaphore->AddRef( pOwner );
}

///////////////////////////////////////////////////////////////////////////////
SJobFinishedConditionVariable* JobManager::CJobManager::GetSemaphore( JobManager::TSemaphoreHandle nSemaphoreHandle, volatile const void *pOwner )
{	
	uint32 nIndex = JobManager::Detail::SemaphoreHandleToIndex(nSemaphoreHandle);
	assert(nIndex<nSemaphorePoolSize);
	
	return &m_JobSemaphorePool[nIndex];
}

///////////////////////////////////////////////////////////////////////////////
void JobManager::CJobManager::DumpJobList()
{
	int i = 1;
	CryLogAlways("== JobManager registered Job List ==");
	for( std::set<JobManager::SJobStringHandle>::iterator it = m_registeredJobs.begin() ; it != m_registeredJobs.end() ; ++it )
	{
		CryLogAlways("%3d. %s", i++, it->cpString);
	}
}

///////////////////////////////////////////////////////////////////////////////
void JobManager::CJobManager::GetBlockingPushPullEA( SSPUDriverParameter *pSPUDriverParameter )
{
	if(m_pBlockingBackEnd)
	{
		JobManager::BlockingBackEnd::CBlockingBackEnd *pBlockingBackend = static_cast<BlockingBackEnd::CBlockingBackEnd*>(m_pBlockingBackEnd);

		pSPUDriverParameter->eaBlockingJobQueuePush = alias_cast<unsigned int>(&pBlockingBackend->m_JobQueue.push);
		pSPUDriverParameter->eaBlockingJobQueuePull = alias_cast<unsigned int>(&pBlockingBackend->m_JobQueue.pull);
		pSPUDriverParameter->eaBlockingQueueSemaphore = alias_cast<unsigned int>(&pBlockingBackend->m_Semaphore);		
		pSPUDriverParameter->eaJobSlotStateBaseBlocking = alias_cast<unsigned int>(pBlockingBackend->m_JobQueue.jobInfoBlockStates);		
		pSPUDriverParameter->eaFallbackInfoBlockBlockingList = alias_cast<unsigned int>(pBlockingBackend->m_pRegularWorkerFallbacks);		
	}
	else
	{
		pSPUDriverParameter->eaBlockingJobQueuePush = ~0;
		pSPUDriverParameter->eaBlockingJobQueuePull = ~0;
		pSPUDriverParameter->eaBlockingQueueSemaphore = ~0;
		pSPUDriverParameter->eaJobSlotStateBaseBlocking = ~0;
		pSPUDriverParameter->eaFallbackInfoBlockBlockingList = ~0;
	}
}

///////////////////////////////////////////////////////////////////////////////
void JobManager::CJobManager::AddBlockingFallbackJob( JobManager::SInfoBlock* pInfoBlock, uint32 nWorkerThreadID )
{
	assert(m_pFallBackBackEnd);
	static_cast<BlockingBackEnd::CBlockingBackEnd*>(m_pBlockingBackEnd)->AddBlockingFallbackJob(pInfoBlock,nWorkerThreadID);
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
TLS_DEFINE(uint32, gWorkerThreadId );
TLS_DEFINE(uintptr_t, gFallbackInfoBlocks );

///////////////////////////////////////////////////////////////////////////////
namespace JobManager {
namespace detail {

	enum { eWorkerThreadMarker = 0x80000000 };
	uint32 mark_worker_thread_id( uint32 nWorkerThreadID )			{ return nWorkerThreadID | eWorkerThreadMarker; }
	uint32 unmark_worker_thread_id( uint32 nWorkerThreadID )		{ return nWorkerThreadID & ~eWorkerThreadMarker; }
	bool is_marked_worker_thread_id( uint32 nWorkerThreadID )		{ return (nWorkerThreadID & eWorkerThreadMarker) != 0; }
} // namespace detail
} // namespace JobManager

///////////////////////////////////////////////////////////////////////////////
void JobManager::detail::SetWorkerThreadId( uint32 nWorkerThreadId )
{
	TLS_SET(gWorkerThreadId, mark_worker_thread_id(nWorkerThreadId) );
}

///////////////////////////////////////////////////////////////////////////////
uint32 JobManager::detail::GetWorkerThreadId()
{
	uint32 nID = TLS_GET(uint32,gWorkerThreadId);
	return is_marked_worker_thread_id(nID) ? unmark_worker_thread_id(nID) : ~0;	
}

///////////////////////////////////////////////////////////////////////////////
void JobManager::detail::PushToFallbackJobList( JobManager::SInfoBlock *pInfoBlock )
{
	pInfoBlock->pNext = (JobManager::SInfoBlock*)TLS_GET(uintptr_t,gFallbackInfoBlocks);
	TLS_SET(gFallbackInfoBlocks, (uintptr_t)pInfoBlock);		
}

JobManager::SInfoBlock* JobManager::detail::PopFromFallbackJobList()
{
	JobManager::SInfoBlock *pRet = (JobManager::SInfoBlock*)TLS_GET(uintptr_t,gFallbackInfoBlocks);
	IF(pRet != NULL,0)
	{
		TLS_SET(gFallbackInfoBlocks, (uintptr_t)pRet->pNext);		
	}
	return pRet;
}
///////////////////////////////////////////////////////////////////////////////
int JobManager::PrintOut( const char *fmt, ... )
{
	va_list args;
	va_start(args, fmt);
	int ret = 0;		
	ret = vprintf(fmt, args);
	va_end(args);
	return ret;
}



